/* License Notice:
**
** This program is free software: you can redistribute it and/or modify
**    it under the terms of the GNU General Public License as published by
**    the Free Software Foundation, either version 3 of the License, or
**    (at your option) any later version.
** This program is distributed in the hope that it will be useful,
**   but WITHOUT ANY WARRANTY; without even the implied warranty of
**   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
**   GNU General Public License for more details.
** You should have received a copy of the GNU General Public License
**   along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

/**
 * @file audio_interfaces.hpp
 * @author TooOld2Rock'nRoll
 * @date 2024/08/09
 * @brief Interfaces for the ArcadeFighter's Audio API.
 * @remarks It's not elegant to implement so much code in the header but we can't have templates on the source and
 *    still package everything as a static library.
 *
 * @todo Volume may be better at the Container than source, although Mix Channels can be set individually....
 */
#ifndef _AUDIO_INTERFACES_HPP_
#define _AUDIO_INTERFACES_HPP_

/*---- Includes ----*/
#include <atomic>
#include <fmt/core.h>

#include <SDL2/SDL_mixer.h>

#include "toolbox/time_keep.h"


/*---- Defines ----*/
///Used to signal a container that is not using any audio track and is in the virtual list.
#define VIRTUAL_TRACK_ID    -1
///Used to signal a container that doesn't need tracks to playback or is not in playback at all.
#define INVALID_TRACK_ID    -2


/*---- Typedefs ----*/
///A more elegant name to keep track of Audio Track IDs used to play some containers.
typedef int AudioTrackID_t;


/*---- Enumerations ----*/
extern "C" {
/**
 * @brief These are the expected states for a sound container to be in.
 *
 * @warning There is an array hidden at the audio.cpp file with names as strings for this enum, remember that the array
 *            must be updated if this enum changes.
 */
typedef enum _sound_state_
{
    SOUND_LOADED = 0,   ///<Audio container ready to start playing.
    SOUND_FADING_IN,    ///<Audio container is fading in as it starts playing.
    SOUND_PLAYING,      ///<Audio container is playing.
    SOUND_PAUSED,       ///<Audio container playback is paused.
    SOUND_FADING_OUT,   ///<Audio container is in the process of ending and fading out.
    SOUND_STOPPED,      ///<Audio container has finished playing.
    _sound_state_max    ///<Invalid state and keeps count of the enumeration size.
} sound_state_e;
} //extern "C"


/*---- Class Declaration ----*/
/**
 * @brief Alias to audio sources data.
 *
 * This is the data that is passed around and never copied.<br>
 * It should not hold state information, just carry the required data to make playback possible.<br>
 * Probably overkill, but since SDL2 Mixer is C native and has no constructor/destructors, this offers the opportunity
 *   for the user to extend the available formats supported and how they are handled if desired.
 *
 * @remarks Requires the implementation of a constructor and a destructor in the child class.
 *
 * @tparam SourceType - one of SDL Mixer sound types available.
 */
template <typename SourceType>
class AudioSourceData
{
    protected:
        SourceType p_native_audio_data = nullptr; ///<Pointer to the actual native sound data.
        double d_length = 0.f; ///<The length of the audio source in sec.

        /** @brief This specific audio source playback volume, from 0 to 100.
         * It will still be mixed against any other volume regulation, like the Master Volume.
         */
        unsigned short us_volume = 100;

        /** @brief Hide the default constructor to be sure there will never be a empty source data. */
        explicit AudioSourceData () {  }
        /** @brief Hide the default destructor to be sure user will implement memory release. */
        virtual ~AudioSourceData () {  }

    public:
        //requires a explicit AudioSourceData (const char *file_path);
        //requires a ~AudioSourceData ();

        /** @brief Returns a pointer to the actual native sound data. */
        SourceType getData () { return this->p_native_audio_data; }

        /** @brief Returns the duration of the audio source in seconds. */
        double getLength () const { return this->d_length; }

        /** @brief Sets the AudioSource playback volume (from 0 to 100). */
        virtual void setVol (unsigned short volume) = 0;
        /** @brief Returns this AudioSource current volume level (0-100). */
        unsigned short getVol () const { return this->us_volume; }
};//END AudioSourceData


/**
 * @brief Interface that intermediate communication with audio sources in playback.
 *
 * @tparam SourceDataType - one of the available audio sources implemented.
 */
template <typename SourceDataType>
class AudioContainer
{
    protected:
        SourceDataType *p_audio_source = nullptr; ///<Pointer to the sound data.

        AudioTrackID_t _using_track = INVALID_TRACK_ID; ///<Which track is being used to playback the source.
        std::atomic<sound_state_e> e_state = sound_state_e::_sound_state_max; ///<This Sound current state;

        bool b_loop = false; ///<If the audio is set to play on loop.
        unsigned u_repeat = 1; ///<How many times the audio should play on repeat.
        std::atomic<unsigned> u_repeat_count = 0; ///<Keep track of how many times it has to repeat before stopping.

        std::atomic<double> d_playback_time = 0.f; ///<current playback time in seconds.
        double d_fadein = 0.f; ///<How long to fade the audio when playback starts.
        double d_fadeout = 0.f; ///<How long to fade the audio at the end of playback.

        /** @brief Regulates state changes for AudioContainers.
         * @remarks Setting SOUND_STOPPED should <i>always</i> be accepted! It will zero out all playback state variables.
         *
         * This method is intended both to inform the user that the audio API is trying to do something with the provided
         *   audio source but also to offer an opportunity to set other environment needs to your system and validate the
         *   state change before the API proceeds to actually execute the desired action.
         *
         * @param new_state - the state the track will go to.
         * @return <b>true</b> if state change was accepted and the procedure may proceed or <b>false</b> if anything is wrong.
         */
        virtual bool setState (sound_state_e new_state) = 0;

        /** @brief Update the Audio container based on time elapsed from last loop iteration.
         * @param delta_t - time in seconds elapsed since last loop iteration.
         */
        virtual void update (double delta_t) = 0;


    public:
        /** @brief Default Constructor. */
        AudioContainer () { this->e_state = sound_state_e::_sound_state_max; };

        /** @brief Constructor with audio source.
         * @param data - pointer to the audio source data to be saved.
         */
        AudioContainer (SourceDataType *data) : AudioContainer() { this->setSource (data); }

        /** @brief Destructor. */
        virtual ~AudioContainer () {  }

        /** @brief Sets the actual audio data to be used for playback.
         * @param data - pointer to the audio source data to be saved (accepts null).
         * @throws std::logic_error - in case the current source is in the wrong state.
         */
        void setSource (SourceDataType *data);
        /** @brief Returns a pointer to the actual sound data. */
        SourceDataType* getSource () { return this->p_audio_source; }

        /** @brief Returns the current AudioContainer state. */
        sound_state_e getState() const { return this->e_state; }

        /** @brief Time in seconds to fade in  (linear fade) the playback. */
        void setFadeIn (double fade_in) { this->d_fadein = fade_in; }
        /** @brief Time in seconds to fade out  (linear fade) the playback. */
        void setFadeOut (double fade_out) { this->d_fadeout = fade_out; }

        /** @brief Set to <b>true</b> to should loop indefinitely, <b>false</b> to play only once and stop. */
        void loop (bool should_loop) { this->b_loop = should_loop; }
        /** @brief <b>true</b> if AudioTrack is set to play on loop. */
        bool isOnLoop () const { return this->b_loop; }

        /** @brief Sets the number of times to repeat playback before stopping (can be overwritten wile playing). */
        void repeat (unsigned count) { this->u_repeat = count; }
        /** @brief Gets the counter of how many times the audio will playback on repeat before stopping. */
        unsigned remainingRepeatCount () const { return this->u_repeat_count; }

        /** @brief Returns the playback time in seconds. */
        double playbackTime () const { return this->d_playback_time; }
};//END AudioContainer


/*---- Class Implementation ----*/
template <typename SourceDataType>
void AudioContainer<SourceDataType>::setSource (SourceDataType *data)
{
    if (this->e_state != sound_state_e::_sound_state_max &&
        this->e_state != sound_state_e::SOUND_LOADED &&
        this->e_state != sound_state_e::SOUND_STOPPED)
        throw std::logic_error (fmt::format ("{0}:{1} - Audio source is in playback, can't change data source!", __LINE__, __FILE__));

    this->p_audio_source = data;
    this->e_state = (data ? sound_state_e::SOUND_LOADED : sound_state_e::_sound_state_max);
}//End setData ()

#endif //_AUDIO_INTERFACES_HPP_

